package com.jeasonzhao.commons.utils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

import com.jeasonzhao.commons.basic.StringCollection;

public class ClassLoaderUtil
{
    public static class ClassLoadException extends Exception
    {
        private static final long serialVersionUID = 1L;

        public ClassLoadException()
        {
            super();
        }

        public ClassLoadException(String message)
        {
            super(message);
        }

        public ClassLoadException(Throwable cause)
        {
            super(cause);
        }

        public ClassLoadException(String message,Throwable cause)
        {
            super(message,cause);
        }
    }

    private static Field g_fieldClasses = null;
    private static Method g_methodAddURL = null;
    static
    {
        try
        {
            g_fieldClasses = ClassLoader.class.getDeclaredField("classes");
            g_methodAddURL = URLClassLoader.class.getDeclaredMethod("addURL",new Class[]
                {URL.class});
            g_fieldClasses.setAccessible(true);
            g_methodAddURL.setAccessible(true);
        }
        catch(NoSuchMethodException ex)
        {
            ex.printStackTrace();
        }
        catch(SecurityException ex)
        {
            ex.printStackTrace();
        }
        catch(NoSuchFieldException ex)
        {
            ex.printStackTrace();
        }
    }

    public static List<Class<?>> getLoadedClasses(Class<?> cls)
        throws ClassLoadException
    {
        return getLoadedClasses(cls == null
                                ? ClassLoaderUtil.class.getClassLoader()
                                : cls.getClassLoader());
    }

    /**
     * Get classed witch have been loaded into the current JVM by the specified ClassLoader.
     * @param cl ClassLoader If this parameter is null,this function will throw a ClassLoadException exception.
     * @return List A list of Class.
     * @throws ClassLoadException The parameter "cl" is null or can not get value from the field named "classes";
     */
    @SuppressWarnings("unchecked")
    public static List<Class<?>> getLoadedClasses(ClassLoader loader)
        throws ClassLoadException
    {
        if(loader == null)
        {
            loader = ClassLoaderUtil.class.getClassLoader();
        }
        try
        {
            return(List<Class<?>>) g_fieldClasses.get(loader);
        }
        catch(Exception e)
        {
            throw new ClassLoadException("Can not get classes from the ClassLoader specified. ",e);
        }
    }

    public static StringCollection getClassPathsInclude(StringCollection inc)
    {
        StringCollection init = getClassPaths();
        for(int n = 0;inc != null && n < inc.size();n++)
        {
            String strCheck = inc.elementAt(n);
            for(int x = 0;null != strCheck && x < init.size();x++)
            {
                String strInit = init.elementAt(x);
                boolean bEquals = strCheck.equals(strInit) || Pattern.matches(strCheck,strInit);
                if(bEquals == false)
                {
                    init.removeElementAt(x);
//                    x = x > 0 ? x - 1 : 0;
                    break;
                }
            }
        }
        return init;
    }

    public static StringCollection getClassPathsExclude(StringCollection exl)
    {
        StringCollection init = getClassPaths();
        for(int n = 0;exl != null && n < exl.size();n++)
        {
            String strCheck = exl.elementAt(n);
            for(int x = 0;null != strCheck && x < init.size();x++)
            {
                String strInit = init.elementAt(x);
                boolean bEquals = strCheck.equals(strInit) || Pattern.matches(strCheck,strInit);
                if(bEquals == false)
                {
                    init.removeElementAt(x);
//                    x = x > 0 ? x - 1 : 0;
                    break;
                }
            }
        }
        return init;
    }

    public static StringCollection getClassPaths()
    {
        HashSet<String> urls = getAllClassPathsUrl();
        Object[] objs = new Object[urls.size()];
        urls.toArray(objs);
        StringCollection ret = new StringCollection();
        for(int n = 0;n < objs.length;n++)
        {
            String str = objs[n].toString();
            if(str.startsWith("file:/"))
            {
                str = str.substring("file:/".length());
            }
            //System.out.print(str);
            ret.addElement((new File(str)).getAbsolutePath());
        }
        return ret;
    }

    private static HashSet<String> getAllClassPathsUrl()
    {
        HashSet<String> ret = new HashSet<String>();
        try
        {
//            processURLClassPath(ret,Launcher.getBootstrapClassPath().getURLs(),null);//@@JDK1.5
            processURLClassPath(ret,((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs(),null);
            processURLClassPath(ret,((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs(),null);
            processURLClassPath(ret,((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURLs(),null);
        }
        catch(Exception ex)
        {
        	ex.printStackTrace();
        }
        return ret;
    }

    private static void processURLClassPath(HashSet<String> hash,URL[] cur,String[] strIncludeJars)
    {
        for(int nIndex = 0;cur != null && nIndex < cur.length;nIndex++)
        {
            if(null != cur[nIndex] && hash.contains(cur[nIndex].toString()) == false)
            {
                boolean bInclude = true;
                if(null != strIncludeJars && strIncludeJars.length > 0)
                {
                    bInclude = false;
                    for(int x = 0;x < strIncludeJars.length;x++)
                    {
                        if(java.util.regex.Pattern.matches(strIncludeJars[x],cur[nIndex].toString()))
                        {
                            bInclude = true;
                            break;
                        }
                    }
                }
                if(bInclude)
                {
                    hash.add(cur[nIndex].toString());
                }
            }
        }
    }

    public static void addClassPath(URL url)
        throws ClassLoadException
    {
        if(null == url)
        {
            return;
        }
        if(getAllClassPathsUrl().contains(url.toString()))
        {
            return;
        }
        try
        {
            g_methodAddURL.invoke((URLClassLoader) ClassLoader.getSystemClassLoader().getParent(),new Object[]
                                  {url});
        }
        catch(Exception e)
        {
            throw new ClassLoadException(e);
        }
    }

//    public static void addClassPath(String strFileName)
//        throws ClassLoadException
//    {
//        try
//        {
//            addClassPath((new java.io.File(strFileName)).toURI().toString());
//        }
//        catch(ClassLoadException e)
//        {
//            throw e;
//        }
//        catch(Exception e)
//        {
//            throw new ClassLoadException(e);
//        }
//    }
//
//    public static void addClassPath(File fileJar)
//        throws ClassLoadException
//    {
//        try
//        {
//            addClassPath(fileJar.toURI().toString());
//        }
//        catch(ClassLoadException e)
//        {
//            throw e;
//        }
//        catch(Exception e)
//        {
//            throw new ClassLoadException(e);
//        }
//    }

//    private static StringCollection findClassesNameFromResource(String strResource)
//        throws ClassLoadException
//    {
//        //ZipFile file=new
//        ResourceHelper.getResourceAsStream(strResource);
//        StringCollection ret = new StringCollection();
//            try
//            {
//                JarFile jar = new JarFile(f.getAbsoluteFile());
//                Enumeration e = jar.entries();
//                while (e.hasMoreElements())
//                {
//                    JarEntry et = (JarEntry) e.nextElement();
//                    if (et.isDirectory() || et.getName().endsWith(".class") == false || et.getName().indexOf("$") >= 0)
//                    {
//                        continue;
//                    }
//                    String strClassName = et.getName().replaceAll("/", ".");
//                    strClassName = strClassName.substring(0, strClassName.length() - ".class".length());
//                    ret.addElement(strClassName);
//                }
//            }
//            catch (IOException exce)
//            {
//                throw new ClassLoadException("File not validate!" + strPath, exce);
//            }
//            return ret;
//
//    }
    public static StringCollection findClassesName(String strPath)
        throws ClassLoadException
    {
        if(null == strPath)
        {
            return null;
        }
        File f = new File(strPath);
        if(f.exists() == false)
        {
            try
            {
                f = ResourceHelper.file(strPath);
            }
            catch(Exception excep)
            {
//                excep.printStackTrace();
                return null;
            }
        }
        if(f.exists() == false)
        {
            return null;
        }
        if(f.isDirectory())
        {
            return findClassesNameFromLocalDir(strPath);
        }
        else
        {
            StringCollection ret = new StringCollection();
            try
            {
                JarFile jar = new JarFile(f.getAbsoluteFile());
                Enumeration<?> e = jar.entries();
                while(e.hasMoreElements())
                {
                    JarEntry et = (JarEntry) e.nextElement();
                    if(et.isDirectory() || et.getName().endsWith(".class") == false || et.getName().indexOf("$") >= 0)
                    {
                        continue;
                    }
                    String strClassName = et.getName().replaceAll("/",".");
                    strClassName = strClassName.substring(0,strClassName.length() - ".class".length());
                    ret.addElement(strClassName);
                }
            }
            catch(IOException exce)
            {
                throw new ClassLoadException("File not validate!" + strPath,exce);
            }
            return ret;
        }
    }

    public static StringCollection findClassesNameFromLocalDir(String strPath)
    {
        StringCollection ret = new StringCollection();
        ret = findClassesFilesFromLocalDir(strPath,ret,null);
        return ret;
    }

    private static StringCollection findClassesFilesFromLocalDir(
        String strPath,
        StringCollection col,
        String strRootName)
    {
        if(null == strPath)
        {
            return col;
        }
        java.io.File dir = new File(strPath);
        if(dir.exists() == false)
        {
            return col;
        }
        if(null == strRootName)
        {
            strRootName = dir.getAbsolutePath();
        }
        if(null == col)
        {
            col = new StringCollection();
        }
        if(dir.isFile())
        {
            String strCheckName = dir.getAbsolutePath().substring(strRootName.length() + 1);
            if(strCheckName.endsWith(".class") && strCheckName.indexOf("$") < 0)
            {
                strCheckName = strCheckName.replaceAll("\\\\","\\.");
                strCheckName = strCheckName.substring(0,strCheckName.length() - ".class".length());
                col.addElement(strCheckName);
                //System.out.println(col.size() + "ClassName >>" + strCheckName);
            }
        }
        else
        {
            File[] subs = dir.listFiles();
            for(int n = 0;n < subs.length;n++)
            {
                col = findClassesFilesFromLocalDir(subs[n].getAbsolutePath(),col,strRootName);
            }
        }
        return col;
    }

    @SuppressWarnings("unchecked")
    public static <T> T createInstance(String strClassName,Object ...parameters)
        throws ClassLoadException
    {
        try
        {
            Class<?> cls = Class.forName(strClassName);
            if(null != parameters)
            {
                for(Constructor<?> ctr : cls.getConstructors())
                {
                    Type[] t = ctr.getGenericParameterTypes();
                    if(null != t && t.length == parameters.length)
                    {
                        return(T) ctr.newInstance(parameters);
                    }
                }
            }
            return(T) cls.newInstance();
        }
        catch(Exception e)
        {
            throw new ClassLoadException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T createInstance(Class<?> cls,Object ...parameters)
        throws ClassLoadException
    {
        try
        {
            if(null != parameters)
            {
                for(Constructor<?> ctr : cls.getConstructors())
                {
                    Type[] t = ctr.getGenericParameterTypes();
                    if(null != t && t.length == parameters.length)
                    {
                        return(T) ctr.newInstance(parameters);
                    }
                }
            }
            return(T) cls.newInstance();
        }
        catch(Exception e)
        {
            throw new ClassLoadException(e);
        }
    }

    public static final boolean isContainsDefaultConstructor(Class<?> cls)
    {
        if(null == cls)
        {
            return false;
        }
        if(cls.isInterface()
           || cls.isAnnotation()
           || cls.isAnonymousClass()
           || cls.isArray()
           || cls.isEnum()
           || cls.isLocalClass()
           || cls.isMemberClass()
           || cls.isPrimitive())
        {
            return false;
        }
        try
        {
            Constructor<?> ct = cls.getConstructor();
            if(null == ct)
            {
                return false;
            }
            return java.lang.reflect.Modifier.isPublic(ct.getModifiers());
        }
        catch(SecurityException ex)
        {
            return false;
        }
        catch(NoSuchMethodException ex)
        {
            return false;
        }
    }

}
